1
2
3
4
5
6
7
8
9
10
11
12
13 package org.apache.tapestry5.internal.pageload;
14
15 import org.apache.tapestry5.Binding;
16 import org.apache.tapestry5.internal.services.ComponentInstantiatorSource;
17 import org.apache.tapestry5.internal.services.Instantiator;
18 import org.apache.tapestry5.internal.structure.*;
19 import org.apache.tapestry5.ioc.Invokable;
20 import org.apache.tapestry5.ioc.Location;
21 import org.apache.tapestry5.ioc.OperationTracker;
22 import org.apache.tapestry5.ioc.Resource;
23 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
24 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
25 import org.apache.tapestry5.ioc.internal.util.TapestryException;
26 import org.apache.tapestry5.ioc.util.ExceptionUtils;
27 import org.apache.tapestry5.ioc.util.IdAllocator;
28 import org.apache.tapestry5.model.ComponentModel;
29 import org.apache.tapestry5.model.EmbeddedComponentModel;
30 import org.apache.tapestry5.runtime.RenderCommand;
31 import org.apache.tapestry5.services.ComponentClassResolver;
32 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
33
34 import java.util.List;
35 import java.util.Map;
36
37 class ComponentAssemblerImpl implements ComponentAssembler
38 {
39 private final ComponentAssemblerSource assemblerSource;
40
41 private final ComponentInstantiatorSource instantiatorSource;
42
43 private final ComponentClassResolver componentClassResolver;
44
45 private final Instantiator instantiator;
46
47 private final ComponentPageElementResources resources;
48
49 private final List<PageAssemblyAction> actions = CollectionFactory.newList();
50
51 private final IdAllocator allocator = new IdAllocator();
52
53 private final OperationTracker tracker;
54
55 private final boolean strictMixinParameters;
56
57 private Map<String, String> publishedParameterToEmbeddedId;
58
59 private Map<String, EmbeddedComponentAssembler> embeddedIdToAssembler;
60
61 public ComponentAssemblerImpl(ComponentAssemblerSource assemblerSource,
62 ComponentInstantiatorSource instantiatorSource, ComponentClassResolver componentClassResolver,
63 Instantiator instantiator, ComponentPageElementResources resources, OperationTracker tracker, boolean strictMixinParameters)
64 {
65 this.assemblerSource = assemblerSource;
66 this.instantiatorSource = instantiatorSource;
67 this.componentClassResolver = componentClassResolver;
68 this.instantiator = instantiator;
69 this.resources = resources;
70 this.tracker = tracker;
71 this.strictMixinParameters = strictMixinParameters;
72 }
73
74 public ComponentPageElement assembleRootComponent(final Page page)
75 {
76 return tracker.invoke("Assembling root component for page " + page.getName(),
77 new Invokable<ComponentPageElement>()
78 {
79 public ComponentPageElement invoke()
80 {
81 return performAssembleRootComponent(page);
82 }
83 });
84 }
85
86 private ComponentPageElement performAssembleRootComponent(Page page)
87 {
88 PageAssembly pageAssembly = new PageAssembly(page);
89
90 long startTime = System.currentTimeMillis();
91
92 try
93 {
94 pageAssembly.componentCount++;
95 pageAssembly.weight++;
96
97 ComponentPageElement newElement = new ComponentPageElementImpl(pageAssembly.page, instantiator, resources);
98
99 pageAssembly.componentName.push(new ComponentName(pageAssembly.page.getName()));
100
101 addRootComponentMixins(pageAssembly, newElement);
102
103 pushNewElement(pageAssembly, newElement);
104
105 runActions(pageAssembly);
106
107 popNewElement(pageAssembly);
108
109
110
111
112
113
114 int count = pageAssembly.deferred.size();
115 for (int i = count - 1; i >= 0; i--)
116 {
117 PageAssemblyAction action = pageAssembly.deferred.get(i);
118
119 pageAssembly.weight++;
120
121 action.execute(pageAssembly);
122 }
123
124 page.setStats(new Page.Stats(System.currentTimeMillis() - startTime, pageAssembly.componentCount, pageAssembly.weight));
125
126 return pageAssembly.createdElement.peek();
127 } catch (RuntimeException ex)
128 {
129 throw new RuntimeException(String.format("Exception assembling root component of page %s: %s", pageAssembly.page.getName(), ExceptionUtils.toMessage(ex)), ex);
130 }
131 }
132
133 private void addRootComponentMixins(PageAssembly assembly, ComponentPageElement element)
134 {
135 for (String className : instantiator.getModel().getMixinClassNames())
136 {
137 assembly.weight++;
138
139 Instantiator mixinInstantiator = instantiatorSource.getInstantiator(className);
140
141 ComponentModel model = instantiator.getModel();
142 element.addMixin(InternalUtils.lastTerm(className), mixinInstantiator, model.getOrderForMixin(className));
143 }
144 }
145
146 public void assembleEmbeddedComponent(final PageAssembly pageAssembly,
147 final EmbeddedComponentAssembler embeddedAssembler, final String embeddedId, final String elementName,
148 final Location location)
149 {
150 ComponentName containerName = pageAssembly.componentName.peek();
151
152 final ComponentName embeddedName = containerName.child(embeddedId.toLowerCase());
153
154 final String componentClassName = instantiator.getModel().getComponentClassName();
155
156 String description = String.format("Assembling component %s (%s)", embeddedName.completeId, componentClassName);
157
158 tracker.run(description, new Runnable()
159 {
160 public void run()
161 {
162 ComponentPageElement container = pageAssembly.activeElement.peek();
163
164 try
165 {
166 pageAssembly.componentName.push(embeddedName);
167
168 ComponentPageElement newElement = container.newChild(embeddedId, embeddedName.nestedId,
169 embeddedName.completeId, elementName, instantiator, location);
170
171 pageAssembly.componentCount++;
172 pageAssembly.weight++;
173
174 pushNewElement(pageAssembly, newElement);
175
176 int mixinCount = embeddedAssembler.addMixinsToElement(newElement);
177
178 pageAssembly.weight += mixinCount;
179
180 runActions(pageAssembly);
181
182 popNewElement(pageAssembly);
183
184 pageAssembly.componentName.pop();
185 } catch (RuntimeException ex)
186 {
187 throw new TapestryException(String.format("Exception assembling embedded component '%s' (of type %s, within %s): %s",
188 embeddedId,
189 componentClassName,
190 container.getCompleteId(),
191 ExceptionUtils.toMessage(ex)), location, ex);
192 }
193 }
194 });
195 }
196
197 private void pushNewElement(PageAssembly pageAssembly, final ComponentPageElement componentElement)
198 {
199
200 pageAssembly.activeElement.push(componentElement);
201
202
203 pageAssembly.createdElement.push(componentElement);
204
205 BodyPageElement shunt = new BodyPageElement()
206 {
207 public void addToBody(RenderCommand element)
208 {
209 componentElement.addToTemplate(element);
210 }
211 };
212
213 pageAssembly.bodyElement.push(shunt);
214 }
215
216 private void popNewElement(PageAssembly pageAssembly)
217 {
218 pageAssembly.bodyElement.pop();
219 pageAssembly.activeElement.pop();
220
221
222 }
223
224 private void runActions(PageAssembly pageAssembly)
225 {
226 for (PageAssemblyAction action : actions)
227 {
228 pageAssembly.weight++;
229 action.execute(pageAssembly);
230 }
231 }
232
233 public ComponentModel getModel()
234 {
235 return instantiator.getModel();
236 }
237
238 public void add(PageAssemblyAction action)
239 {
240 actions.add(action);
241 }
242
243 public void validateEmbeddedIds(Map<String, Location> componentIds, Resource templateResource)
244 {
245 Map<String, Boolean> embeddedIds = CollectionFactory.newCaseInsensitiveMap();
246
247 for (String id : getModel().getEmbeddedComponentIds())
248 embeddedIds.put(id, true);
249
250 for (String id : componentIds.keySet())
251 {
252 allocator.allocateId(id);
253 embeddedIds.remove(id);
254 }
255
256 if (!embeddedIds.isEmpty())
257 {
258
259 String className = getModel().getComponentClassName();
260
261 throw new RuntimeException(String.format("Embedded component(s) %s are defined within component class %s (or a super-class of %s), but are not present in the component template (%s).",
262 InternalUtils.joinSorted(embeddedIds.keySet()),
263 className,
264 InternalUtils.lastTerm(className),
265 templateResource));
266 }
267 }
268
269 public String generateEmbeddedId(String componentType)
270 {
271
272
273 int slashx = componentType.lastIndexOf("/");
274
275 String baseId = componentType.substring(slashx + 1).toLowerCase();
276
277
278
279
280
281 return allocator.allocateId(baseId);
282 }
283
284 public EmbeddedComponentAssembler createEmbeddedAssembler(String embeddedId, String componentClassName,
285 EmbeddedComponentModel embeddedModel, String mixins, Location location)
286 {
287 try
288 {
289
290 if (InternalUtils.isBlank(componentClassName))
291 {
292 throw new TapestryException(
293 "You must specify the type via t:type, the element, or @Component annotation.", location, null);
294 }
295
296 EmbeddedComponentAssemblerImpl embedded = new EmbeddedComponentAssemblerImpl(assemblerSource,
297 instantiatorSource, componentClassResolver, componentClassName, getSelector(), embeddedModel,
298 mixins, location, strictMixinParameters);
299
300 if (embeddedIdToAssembler == null)
301 embeddedIdToAssembler = CollectionFactory.newMap();
302
303 embeddedIdToAssembler.put(embeddedId, embedded);
304
305 if (embeddedModel != null)
306 {
307 for (String publishedParameterName : embeddedModel.getPublishedParameters())
308 {
309 if (publishedParameterToEmbeddedId == null)
310 publishedParameterToEmbeddedId = CollectionFactory.newCaseInsensitiveMap();
311
312 String existingEmbeddedId = publishedParameterToEmbeddedId.get(publishedParameterName);
313
314 if (existingEmbeddedId != null)
315 {
316 throw new TapestryException(
317 String.format("Parameter '%s' of embedded component '%s' can not be published as a parameter of component %s, as it has previously been published by embedded component '%s'.",
318 publishedParameterName,
319 embeddedId,
320 instantiator
321 .getModel().getComponentClassName(),
322 existingEmbeddedId), location, null);
323 }
324
325 publishedParameterToEmbeddedId.put(publishedParameterName, embeddedId);
326 }
327
328 }
329
330 return embedded;
331 } catch (Exception ex)
332 {
333 throw new TapestryException(String.format("Failure creating embedded component '%s' of %s: %s", embeddedId, instantiator
334 .getModel().getComponentClassName(), ExceptionUtils.toMessage(ex)), location, ex);
335 }
336 }
337
338 public ParameterBinder getBinder(final String parameterName)
339 {
340 final String embeddedId = InternalUtils.get(publishedParameterToEmbeddedId, parameterName);
341
342 if (embeddedId == null)
343 return null;
344
345 final EmbeddedComponentAssembler embededdedComponentAssembler = embeddedIdToAssembler.get(embeddedId);
346
347 final ComponentAssembler embeddedAssembler = embededdedComponentAssembler.getComponentAssembler();
348
349 final ParameterBinder embeddedBinder = embeddedAssembler.getBinder(parameterName);
350
351
352
353
354 if (embeddedBinder != null)
355 {
356 return new ParameterBinder()
357 {
358 public void bind(ComponentPageElement element, Binding binding)
359 {
360 ComponentPageElement subelement = element.getEmbeddedElement(embeddedId);
361
362 embeddedBinder.bind(subelement, binding);
363 }
364
365 public String getDefaultBindingPrefix(String metaDefault)
366 {
367 return embeddedBinder.getDefaultBindingPrefix(metaDefault);
368 }
369 };
370 }
371
372 final ParameterBinder innerBinder = embededdedComponentAssembler.createParameterBinder(parameterName);
373
374 if (innerBinder == null)
375 {
376 String message = String.format("Parameter '%s' of component %s is improperly published from embedded component '%s' " +
377 "(where it does not exist). This may be a typo in the publishParameters attribute of " +
378 "the @Component annotation.", parameterName, instantiator.getModel()
379 .getComponentClassName(), embeddedId);
380
381 throw new TapestryException(message, embededdedComponentAssembler.getLocation(), null);
382 }
383
384
385
386 return new ParameterBinder()
387 {
388 public void bind(ComponentPageElement element, Binding binding)
389 {
390 ComponentPageElement subelement = element.getEmbeddedElement(embeddedId);
391
392 innerBinder.bind(subelement, binding);
393 }
394
395 public String getDefaultBindingPrefix(String metaDefault)
396 {
397 return innerBinder.getDefaultBindingPrefix(metaDefault);
398 }
399 };
400 }
401
402 public ComponentResourceSelector getSelector()
403 {
404 return resources.getSelector();
405 }
406
407 @Override
408 public String toString()
409 {
410 return String.format("ComponentAssembler[%s %s]", instantiator.getModel().getComponentClassName(), getSelector());
411 }
412 }